2008年10月27日
川俣晶の縁側ソフトウェア技術雑記 total 20055 count

C#のジェネリックは基底クラスを型引数に指定した対象に変換できない

Written By: 川俣 晶連絡先

 タイトルの「C#のジェネリックは基底クラスを型引数に指定した対象に変換できない」とは意味が良く分かりませんが。罠っぽい問題に遭遇したのでメモっておきます。

サンプルソース §

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

class A {}

class B : A {}

class Program

{

    private static void method1(A[] array) { }

    private static void method2(List<A> list) { }

    static void Main(string[] args)

    {

        method1(new B[] { }); // ケース1

        method2(new List<B>()); // ケース2

        method1((new B[] { }).ToArray()); // ケース3

    }

}

コンパイル結果 §

エラー 1 'Program.method2(System.Collections.Generic.List<A>)' に最も適しているオーバーロード メソッドには無効な引数がいくつか含まれています。 d:\w\test\ConsoleApplication65\ConsoleApplication65\Program.cs 16 9 ConsoleApplication65

エラー 2 引数 '1': 'System.Collections.Generic.List<B>' から 'System.Collections.Generic.List<A>' に変換できません。 d:\w\test\ConsoleApplication65\ConsoleApplication65\Program.cs 16 17 ConsoleApplication65

解説 §

 ケース1とケース3は通りますが、ケース2は通りません。

 つまり、基底クラスを型として持つ配列への変換は自動的に実行できますが、List<T>への変換はできません。C#プログラミングではA[]とList<A>は似たような存在として使い、本当はA[]が欲しい場合でもList<A>として作成してからToArrayすることも多いと思いますが、このようなケースではList<A>のままでは引数に渡せないことになります。しかし、ToArrayしてしまうと、もはやアイテムの追加はできなくなります。

追記 §

 ではこのようなケースにはどのように対処すべきか、というと、たぶんジェネリックメソッドにして型制約を付けるのが正解ではないかと思いました。以下のような感じです。

 この場合、引数listに格納された要素はAまたはAを継承した値であることが制約されるので、method2内からは"list[0].hoge"のような感じでAに含まれるhogeをキャスト無しで呼び出せます。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

class A { }

class B : A {}

class Program

{

    private static void method1(A[] array) { }

    private static void method2<T>(List<T> list) where T : A { }

    static void Main(string[] args)

    {

        method1(new B[] { });

        method2<B>(new List<B>());

        method1((new B[] { }).ToArray());

    }

}

追記の感想 §

 型制約は滅多に使わないので、思い出すのに時間が掛かってしまいました。